using System;
using System.Collections.Generic;
using System.Text;
using SPTraceView.Model;
using System.Diagnostics;
using System.IO;
using System.Xml;
using System.Runtime.InteropServices;

namespace SPTraceView.Model
{
    internal class MyTraceLogFile
    {
        [DllImport("kernel32", CharSet = CharSet.Auto)]
        static extern int GetDiskFreeSpaceEx(
            string lpDirectoryName, 
            out ulong lpFreeBytesAvailable, 
            out ulong lpTotalNumberOfBytes, 
            out ulong lpTotalNumberOfFreeBytes);


        private string m_LogFilePath;
        private string m_ShadowCopyFilePath;
        private string m_TemplateFilePath;

        private bool m_LoggingDisabled = false;
        private bool m_LowDiskSpaceDetected = false;


        private XmlDocument m_XmlLogFile = new XmlDocument();
        private object m_SyncRoot = new object();

        private const string BLANK_LOG_FILE = "<?xml version=\"1.0\" encoding=\"utf-8\" ?><?xml-stylesheet type=\"text/xsl\" href=\"SPTraceView.xslt\"?><SPTraceViewLog date=\"{0}\" computerName=\"{1}\"/>";

        private int m_ThisLogFileMark = 0;

        private string MyTraceLogPath(DateTime now)
        {
            m_ThisLogFileMark = (now.Year << 10) + (now.Month << 6) + now.Day;
            return Path.GetFullPath(string.Format("{0}\\SPTraceViewLog_{1}_{2}_{3}.xml", AppDomain.CurrentDomain.BaseDirectory, now.Year, now.Month, now.Day));
        }

        private bool NewLogFileRequired()
        {
            DateTime now = DateTime.Now;
            return m_ThisLogFileMark != (now.Year << 10) + (now.Month << 6) + now.Day;
        }

        public MyTraceLogFile()
        {
            DateTime now = DateTime.Now;
            m_LogFilePath = MyTraceLogPath(now);
            m_ShadowCopyFilePath = Path.GetFullPath(string.Format("{0}\\CurrentSPTraceViewLog.xml", AppDomain.CurrentDomain.BaseDirectory));

            m_TemplateFilePath = Path.GetFullPath(AppDomain.CurrentDomain.BaseDirectory + "\\SPTraceView.xslt");

            m_XmlLogFile = new XmlDocument();
            LoadBlankXml(now);

            EnsureXSLT();
        }

        private void EnsureXSLT()
        {
            if (!File.Exists(m_TemplateFilePath))
            {
                string xslt = SPTraceView.Helpers.AssemblyHelper.GetEmbededResource("SPTraceView", "SPTraceView.xslt");
                File.WriteAllText(m_TemplateFilePath, xslt.Trim('\x00'));
            }
        }

        private void LoadBlankXml(DateTime now)
        {
            bool useBlank = true;
            int index = 0;

            while (File.Exists(m_LogFilePath))
            {
                try
                {
                    m_XmlLogFile.Load(m_LogFilePath);
                    Trace.WriteLine("Now logging to file \"" + m_LogFilePath + "\"", "SPTraceView");
                    useBlank = false;
                    break;
                }
                catch (IOException ioex)
                {
                    //The file is locked or cannot be loaded for anotoher reason. So ty finding another free file name

                    if (index > 16)
                    {
                        // Cannot find/open file, logging will be disabled.
                        m_LoggingDisabled = true;
                        Trace.WriteLine("Error loading log file. The logging will be disabled. The error message is: " + ioex.Message, "SPTraceView");
                        useBlank = false;
                        break;
                    }
                    
                    index++;
                    m_LogFilePath = Path.GetFullPath(string.Format("{0}\\SPTraceViewLog_{1}_{2}_{3} ({4}).xml", AppDomain.CurrentDomain.BaseDirectory, now.Year, now.Month, now.Day, index));
                }
            }

            if (useBlank)
            {
                m_XmlLogFile.LoadXml(string.Format(BLANK_LOG_FILE, now.ToString("dd MMM yyyy"), System.Environment.MachineName));
                try
                {
                    m_XmlLogFile.Save(m_LogFilePath);
                    Trace.WriteLine("Now logging to file \"" + m_LogFilePath + "\"", "SPTraceView");
                }
                catch (IOException ioex)
                {
                    m_LoggingDisabled = true;
                    Trace.WriteLine("Error saving log file. The logging will be disabled. The error message is: " + ioex.Message, "SPTraceView");
                }
            }
        }

        public void TraceEvent(
            string serverName,
            ULS.TraceSeverity severity,
            long timeStamp,
            uint processId,
            string exeName,
            string product,
            string category,
            string message,
            Int64 timeCorrection)
        {
            if (m_LoggingDisabled)
                return;

            lock (m_SyncRoot)
            {
                XmlElement traceEl = m_XmlLogFile.CreateElement("TraceEntry");

                XmlAttribute att = m_XmlLogFile.CreateAttribute("severity");
                traceEl.Attributes.Append(att);
                att.Value = severity.ToString();

                att = m_XmlLogFile.CreateAttribute("timestamp");
                traceEl.Attributes.Append(att);
                att.Value = timeStamp.ToString();

                DateTime eventTime = DateTime.FromFileTime(timeStamp + timeCorrection);

                att = m_XmlLogFile.CreateAttribute("time");
                traceEl.Attributes.Append(att);
                att.Value = eventTime.ToString("HH:mm:ss.fffff");

                att = m_XmlLogFile.CreateAttribute("processId");
                traceEl.Attributes.Append(att);
                att.Value = processId.ToString();

                att = m_XmlLogFile.CreateAttribute("exeName");
                traceEl.Attributes.Append(att);
                att.Value = exeName.ToString();

                att = m_XmlLogFile.CreateAttribute("product");
                traceEl.Attributes.Append(att);
                att.Value = product.ToString();

                att = m_XmlLogFile.CreateAttribute("category");
                traceEl.Attributes.Append(att);
                att.Value = category.ToString();

                att = m_XmlLogFile.CreateAttribute("serverName");
                traceEl.Attributes.Append(att);
                att.Value = serverName;

                if (message.IndexOf("]]>") != -1)
                    message = message.Replace("]]>", "))>").Replace("<![CDATA[", "<!(CDATA(");

                XmlCDataSection content = m_XmlLogFile.CreateCDataSection(message);
                traceEl.AppendChild(content);

                if (m_XmlLogFile.DocumentElement.ChildNodes.Count > 0)
                    m_XmlLogFile.DocumentElement.InsertBefore(traceEl, m_XmlLogFile.DocumentElement.ChildNodes[0]);
                else
                    m_XmlLogFile.DocumentElement.AppendChild(traceEl);
            }
        }

        public void Flush()
        {
            Flush(false);
        }

        public void Flush(bool saveShadowCopy)
        {
            lock (m_SyncRoot)
            {
                if (!m_LoggingDisabled)
                {
                    ulong freeBytesAvailable = 0;
                    ulong totalBytes = 0;
                    ulong freeBytes = 0;

                    try
                    {
                        GetDiskFreeSpaceEx(m_LogFilePath.Substring(0, 2), out freeBytesAvailable, out totalBytes, out freeBytes);
                    }
                    catch (Exception)
                    { }

                    //                     Mb     Kb
                    if (freeBytes > 250 * 1024 * 1024)
                    {
                        if (m_LowDiskSpaceDetected)
                        {
                            m_LowDiskSpaceDetected = false;
                            Trace.WriteLine("The low disk space condition was resolved. Continue logging ...", "SPTraceView");
                        }

                        using (FileStream fs = new FileStream(m_LogFilePath, FileMode.Create, FileAccess.Write, FileShare.Read))
                        {
                            m_XmlLogFile.Save(fs);
                        }
                    }
                    else
                    {
                        if (!m_LowDiskSpaceDetected)
                        {
                            m_LowDiskSpaceDetected = true;
                            Trace.WriteLine("A low disk space condition is found. The logging will continue when there are more than 250Mb free.", "SPTraceView");
                        }
                    }
                }

                if (saveShadowCopy)
                {
                    try
                    {
                        if (File.Exists(m_ShadowCopyFilePath))
                            // We need this, because the COPY function will fail for hidden attributes
                            File.SetAttributes(m_ShadowCopyFilePath, FileAttributes.Normal);

                        if (File.Exists(m_LogFilePath))
                            File.Copy(m_LogFilePath, m_ShadowCopyFilePath, true);
                    }
                    finally
                    {
                        if (File.Exists(m_ShadowCopyFilePath))
                            File.SetAttributes(m_ShadowCopyFilePath, FileAttributes.Hidden);
                    }
                }

                if (NewLogFileRequired())
                {
                    DateTime now = DateTime.Now;
                    m_LogFilePath = MyTraceLogPath(now);
                    LoadBlankXml(now);
                }
            }
        }

        public string SaveLogFileForViewing()
        {
            Flush(true);

            EnsureXSLT();

            return m_ShadowCopyFilePath;
        }
    }
}
